/*
 * File:   SceneInitializer.cpp
 * Author: Elevator
 *
 * Created on 13 Июнь 2013 г., 23:54
 */

#include "Initializer.h"
#include <map>
#include "Core/Core.h"
#include "TextureInfo.h"

Scene* Initializer::Initialize( std::string sceneFilename )
{
	return CreateScene( data::LoadScene( sceneFilename ) );
}

Scene* Initializer::CreateScene( const data::Scene* dataScene )
{
	Scene* scene = new Scene( );

	_creatures.clear( );

	for( IteratorPtr<const data::Creature*> creatures( dataScene->GetCreatures( )->GetIterator( ) );
			!creatures->IsEnd( );
			creatures->Next( ) )
	{
		const data::Creature* dataCreature = creatures->Current( );
		Creature* creature = CreateCreature( dataCreature );
		_creatures[dataCreature->GetId( )] = creature;
		scene->AddEntity( creature );
	}

	for( IteratorPtr<data::Player> players( dataScene->GetPlayers( )->GetIterator( ) );
			!players->IsEnd( );
			players->Next( ) )
	{
		const data::Player dataPlayer = players->Current( );
		scene->AddPlayer( CreatePlayer( dataPlayer ) );
	}

	_creatures.clear( );

	scene->AddEntity( new Grid() );

	scene->SetCamera( CreateCamera( dataScene->GetCamera( ) ) );

	const data::Map* dataMap = data::LoadMap( dataScene->GetMapFileName() );
	scene->SetMap( CreateMap( dataMap ) );
	delete dataMap;

	return scene;
}

Camera* Initializer::CreateCamera( const data::Camera* dataCamera )
{
	Camera* camera = new Camera(
		dataCamera->GetPosition( ),
		dataCamera->GetVelocity( ),
		dataCamera->GetScale( ) );

	core::Keyboard::AddListener( camera );
	return camera;
}

Map* Initializer::CreateMap( const data::Map* dataMap )
{
	Map* map = Factory::GetInstance( ).CreateMap( );

	for( IteratorPtr<const data::Obstacle*> obstacles( dataMap->GetObstacles( )->GetIterator( ) );
			!obstacles->IsEnd( );
			obstacles->Next( ) )
	{
		map->AddObstacle( CreateObstacle( obstacles->Current( ) ) );
	}
	return map;
}

Obstacle* Initializer::CreateObstacle( const data::Obstacle* dataObstacle )
{
	Obstacle* obstacle = Factory::GetInstance( ).CreateObstacle( dataObstacle->GetId( ) );

	for( IteratorPtr<const data::Shape*> shapes( dataObstacle->GetShapes( )->GetIterator( ) );
			!shapes->IsEnd( );
			shapes->Next( ) )
	{
		obstacle->AddBody(
			Factory::GetInstance( ).CreateBody(
				physics::IBody::Static,
				Transform::ZERO,
				shapes->Current( )->GetVertices( ),
				1.0f, 1.0f, 1.0f, 1.0f ) );
	}
	return obstacle;
}

Creature* Initializer::CreateCreature( const data::Creature* dataCreature )
{
	Creature* creature = Factory::GetInstance( ).CreateCreature( dataCreature->GetId( ) );

	const data::CreatureDef* dataCreatureDef = data::LoadCreatureDef( dataCreature->GetDefinitionFileName() );;

	Transform creaturePos = dataCreature->GetPosition( );

	_details.clear( );
	_junctions.clear( );
	_parts.clear( );

	for( IteratorPtr<const data::Detail*> dataDetails( dataCreatureDef->GetDetails( )->GetIterator( ) );
			!dataDetails->IsEnd( );
			dataDetails->Next( ) )
	{
		const data::Detail* dataDetail = dataDetails->Current( );
		Detail* detail = CreateDetail( creaturePos, dataDetail );
		_details[dataDetail->GetId( )] = detail;
		_parts[dataDetail->GetId( )] = detail;
		creature->AddPart( detail );
	}

	for( IteratorPtr<const data::Rotor*> dataRotors( dataCreatureDef->GetRotors( )->GetIterator( ) );
			!dataRotors->IsEnd( );
			dataRotors->Next( ) )
	{
		const data::Rotor* dataRotor = dataRotors->Current( );
		Junction* junction = CreateJunction( creaturePos, dataRotor );
		_junctions[dataRotor->GetId( )] = junction;
		_parts[dataRotor->GetId( )] = junction;
		creature->AddPart( junction );
	}

	for( IteratorPtr<data::Link> dataLinks( dataCreatureDef->GetLinks( )->GetIterator( ) );
			!dataLinks->IsEnd( );
			dataLinks->Next( ) )
	{
		data::Link dataLink = dataLinks->Current( );
		IPart* partA = _parts[ dataLink.GetPartAId( ) ];
		IPart* partB = _parts[ dataLink.GetPartBId( ) ];
		creature->LinkParts( partA, partB );
	}
	_details.clear( );
	_junctions.clear( );
	_parts.clear( );

	return creature;
}

Detail* Initializer::CreateDetail( const Transform& creaturePos, const data::Detail* dataDetail )
{
	std::string id = dataDetail->GetId( );
	Transform localPos = dataDetail->GetPosition( );

	const data::DetailDef* dataDetailDef = data::LoadDetailDef( dataDetail->GetDefinitionFileName() );

	data::Texture texData = dataDetailDef->GetTexture( );
	const core::Texture* tex = core::Renderer::GetTextureStore().GetTexture(texData.GetFileName());

	TextureInfo* texInfo = new TextureInfo(tex, texData.GetPosition(), texData.GetWidth(), texData.GetHeight());

	const IIterable<Circle>* circles = dataDetailDef->GetVertices( );
	float mass = dataDetailDef->GetMass( );
	float intertia = dataDetailDef->GetInertia( );
	float friction = dataDetailDef->GetFriction( );
	float restitution = dataDetailDef->GetRestitution( );

	Detail* detail = Factory::GetInstance( ).CreateDetail( id, creaturePos * localPos, texInfo, circles, mass, intertia, friction, restitution );

	for( IteratorPtr<data::Rule> rules( dataDetail->GetRules( )->GetIterator( ) );
			!rules->IsEnd( );
			rules->Next( ) )
	{
		detail->AddRule( CreateRule( rules->Current( ) ) );
	}
	return detail;
}

Junction* Initializer::CreateJunction( const Transform& creaturePos, const data::Rotor* dataRotor )
{
	std::string id = dataRotor->GetId( );
	Vec2 localPos = dataRotor->GetPosition( );

	Detail* detailA = _details[dataRotor->GetDetailAId( )];
	Detail* detailB = _details[dataRotor->GetDetailBId( )];

	const data::RotorDef* dataRotorDef = data::LoadRotorDef( dataRotor->GetDefinitionFileName( ) );

	float torque = dataRotorDef->GetTorque( );

	Junction* junction = Factory::GetInstance( ).CreateJunction( id, detailA, detailB, creaturePos * localPos, torque );

	for( IteratorPtr<data::Rule> rules( dataRotor->GetRules( )->GetIterator( ) );
			!rules->IsEnd( );
			rules->Next( ) )
	{
		junction->AddRule( CreateRule( rules->Current( ) ) );
	}
	return junction;
}

CommandType DataCommandTypeToCommandType( const data::CommandType& dataCommandType )
{
	switch( dataCommandType )
	{
	case data::CommandType::Press:
		return CommandType::Press;
	case data::CommandType::Release:
		return CommandType::Release;
	case data::CommandType::Move:
		return CommandType::Move;
	case data::CommandType::Send:
		return CommandType::Send;
	case data::CommandType::UnknownCommandType:
		return CommandType::UnknownCommandType;
	default:
		return CommandType::UnknownCommandType;
	}
}

Command DataCommandToCommand( const data::Command& dataCommand )
{
	return Command( DataCommandTypeToCommandType( dataCommand.GetType( ) ), dataCommand.GetKey( ) );
}

Action DataActionToAction( const data::Action& dataAction )
{
	switch( dataAction )
	{
	case data::Action::StartLeftRotation:
		return Action::StartLeftRotation;
	case data::Action::StopLeftRotation:
		return Action::StopLeftRotation;
	case data::Action::StartRightRotation:
		return Action::StartRightRotation;
	case data::Action::StopRightRotation:
		return Action::StopRightRotation;
	case data::Action::Grab:
		return Action::Grab;
	case data::Action::WatchCursor:
		return Action::WatchCursor;
	case data::Action::UnknownAction:
		return Action::UnknownAction;
	default:
		return Action::UnknownAction;
	}
}

Rule Initializer::CreateRule( const data::Rule& dataRule )
{
	return Rule(
			   DataCommandToCommand( dataRule.GetCommand( ) ),
			   DataActionToAction( dataRule.GetAction( ) ) );
}

IPlayer* Initializer::CreatePlayer( const data::Player& dataPlayer )
{
	Creature* creature = _creatures[dataPlayer.GetCreatureId( )];
	switch( dataPlayer.GetType( ) )
	{
	case data::PlayerType::EmptyPlayer:
		return new EmptyPlayer( creature );
	case data::PlayerType::HumanPlayer:
	{
		HumanPlayer* human = new HumanPlayer( creature );
		core::Keyboard::AddListener( human );
		return human;
	}
	case data::PlayerType::Unknown:
		throw NotFoundException( "Unknown player type" );
	default:
		throw NotFoundException( "Unknown player type" );
	}
}
